package mirrormonkey.state.module;

import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import mirrormonkey.framework.ConnectionInfo;
import mirrormonkey.framework.SyncAppState;
import mirrormonkey.framework.entity.EntityData;
import mirrormonkey.framework.entity.StaticEntityData;
import mirrormonkey.framework.member.DynamicMemberData;
import mirrormonkey.framework.module.MirrorMonkeyModule;
import mirrormonkey.state.StateUpdateListener;
import mirrormonkey.state.annotations.StateAnnotationPresets;
import mirrormonkey.state.member.outbound.OutboundDynamicMemberStateData;
import mirrormonkey.state.member.outbound.OutboundStaticMemberStateData;
import mirrormonkey.state.messages.StateUpdateMessage;

import com.jme3.network.serializing.Serializer;

public class StateUpdateModule extends MirrorMonkeyModule {

	private final StateUpdateMessageListener updateListener;

	private final Map<StaticEntityData, Collection<OutboundStaticMemberStateData>> updatedFields;

	public StateUpdateModule(SyncAppState appState) {
		super(appState);

		appState.entityProvider.parser
				.parsePresetClass(StateAnnotationPresets.class);

		Serializer.registerClass(StateUpdateMessage.class);

		updateListener = new StateUpdateMessageListener(this);
		appState.eventManager.addMessageListener(updateListener,
				StateUpdateMessage.class);

		updatedFields = new HashMap<StaticEntityData, Collection<OutboundStaticMemberStateData>>();
	}

	public void fieldUpdatedThisFrame(
			OutboundStaticMemberStateData staticMemberStateData) {
		Collection<OutboundStaticMemberStateData> alreadyUpdated = updatedFields
				.get(staticMemberStateData.staticData);
		if (alreadyUpdated == null) {
			alreadyUpdated = new LinkedList<OutboundStaticMemberStateData>();
			updatedFields.put(staticMemberStateData.staticData, alreadyUpdated);
		}
		alreadyUpdated.add(staticMemberStateData);
	}

	@Override
	public void update(float tpf) {
		for (Entry<StaticEntityData, Collection<OutboundStaticMemberStateData>> e : updatedFields
				.entrySet()) {
			StaticEntityData staticData = e.getKey();
			for (EntityData i : staticData.entities) {
				DynamicMemberData[] members = i.getMemberData(staticData);
				List<Integer> updatedFieldIds = new LinkedList<Integer>();
				List<Object> updatedFieldValues = new LinkedList<Object>();
				for (OutboundStaticMemberStateData updatedStaticData : e
						.getValue()) {
					OutboundDynamicMemberStateData dynamicData = (OutboundDynamicMemberStateData) members[updatedStaticData.id];
					Object fieldValue = updatedStaticData.interpreter
							.packData(updatedStaticData.readAccessor
									.readValue(i.entity));
					if (dynamicData.valueUpdated(fieldValue)) {
						updatedFieldIds.add(updatedStaticData.id);
						updatedFieldValues.add(fieldValue);
					}
				}
				if (!updatedFieldIds.isEmpty()) {
					int[] updatedFieldIdsArray = new int[updatedFieldIds.size()];
					int id = 0;
					for (Integer ii : updatedFieldIds) {
						updatedFieldIdsArray[id++] = ii;
					}
					StateUpdateMessage message = new StateUpdateMessage(i.id,
							updatedFieldIdsArray, updatedFieldValues.toArray(),
							appState.timer.getTime(), false);
					Collection<StateUpdateListener> listeners = collectListeners(
							i, staticData);
					for (StateUpdateListener l : listeners) {
						l.beforeOutboundUpdate(i, staticData, message);
					}
					for (ConnectionInfo info : i.getConnections(staticData)) {
						message.estimatedRemoteTime = info.estimatedRemoteTime;
						try {
							info.connection.send(message);
						} catch (Exception ex) {
							ex.printStackTrace();
						}
					}
					for (StateUpdateListener l : listeners) {
						l.afterOutboundUpdate(i, staticData, message);
					}
				}
			}
		}
		updatedFields.clear();
	}

	public Collection<StateUpdateListener> collectListeners(EntityData data,
			StaticEntityData staticData) {
		List<StateUpdateListener> l = new LinkedList<StateUpdateListener>();
		if (StateUpdateListener.class.isInstance(data.entity)) {
			l.add((StateUpdateListener) data.entity);
		}
		appState.listeners.getListeners(StateUpdateListener.class, l);
		staticData.listeners.getListeners(StateUpdateListener.class, l);
		data.listeners.getListeners(StateUpdateListener.class, l);
		return l;
	}

	public Collection<StateUpdateListener> collectListeners(EntityData data,
			StaticEntityData staticData, ConnectionInfo info) {
		Collection<StateUpdateListener> l = collectListeners(data, staticData);
		info.listeners.getListeners(StateUpdateListener.class, l);
		return l;
	}
}
